#include "SceneObject.h"
#include "Files.h"
#include <regex>
#include <sstream>
#include <algorithm>

using namespace std;

SceneObject::SceneObject(){

}

SceneObject::SceneObject(int id, bool stat, glm::vec3 pos, glm::vec3 rot, glm::vec3 sca, Material mat, string matName, list<std::string> modelProps, string mdlName){
	
	SetPosition(pos);
	SetRotation(rot);
	SetScale(sca);

	LoadModel(modelProps, mdlName);
	material = mat;

	material.UseMaterial();

	modelUniLoc = glGetUniformLocation(material.GetProgram().GetProgram(), "modelMatrix");
	viewUniLoc = glGetUniformLocation(material.GetProgram().GetProgram(), "viewMatrix");
	projUniLoc = glGetUniformLocation(material.GetProgram().GetProgram(), "projMatrix");
	colourUniLoc = glGetUniformLocation(material.GetProgram().GetProgram(), "colour");
	tilingUniLoc = glGetUniformLocation(material.GetProgram().GetProgram(), "tilingMultiplier");
		

	//!!!!!!!!!!!!!!!!!!
	// todo: go through each lod and build them too
	//!!!!!!!!!!!!!!!!!
}

void SceneObject::AssignMaterial(Material nMat){
	material = nMat;
}

void SceneObject::LoadModel(list<std::string> modelProps, string mdlName){

	bool definedModel = false;
	ModelProperties properties;
	properties.nocull = false;
	properties.animated = false;
	FileBuffer defaultProps("models/cube.prop");

	list<string>::iterator it = modelProps.begin();
	while (it != modelProps.end()){

		// if it's a commented line, ignore it
		regex reComment("\/\/(\s*.+$)");
		smatch match;
		if (regex_search(*it, match, reComment)){
			++it;
		}
		else{
			regex rePropName("(\\w+[a-z0-9])");
			// weed out the valid properties
			if (regex_search(*it, match, rePropName)){
				string propName = match.str();
						regex reVal("\\=\\s* (.*)");
						printf("%s \n", it->c_str());
						if (regex_search(*it, match, reVal)){
							string value = match[1].str();

							if (propName == "animated"){
								int val;
								istringstream(value) >> val;
								val = std::max(0, std::min(val, 1));
								properties.animated = val;
							}
							else if (propName == "model"){
								definedModel = true;
								properties.model = value;
							}
							else if (propName == "nocull"){
								int val;
								istringstream(value) >> val;
								val = std::max(0, std::min(val, 1));
								properties.nocull = val;
							}
							else if (propName == "lod"){
								LODModel nLOD;

								regex reLodId("([0-9])\\:");
								if (regex_search(value, match, reLodId)){

									int id;
									istringstream(match[1]) >> id;
									nLOD.id = id;

									regex reDist("\\:\\s*(\\w+[0-9])");
									if (regex_search(value, match, reDist)){
										int dist;
										istringstream(match[1]) >> dist;
										nLOD.dist = dist;

										std::regex rePath("\\{(.*)\\}");
										if (regex_search(value, match, rePath)){
											string path = match[1].str();
											path.erase(std::remove_if(path.begin(), path.end(), isspace), path.end());
											properties.lods.push_back(nLOD);
										}
										else{
											Engine::LogError("Unable to read LOD model for " + mdlName, Engine::errorType::TYPE_ERROR);
											LoadModel(defaultProps.ReadLines(), "models/default");
										}

									}else{
										Engine::LogError("Unable to read LOD model for " + mdlName, Engine::errorType::TYPE_ERROR);
										LoadModel(defaultProps.ReadLines(), "models/default");
									}
								
								}else{
									Engine::LogError("Unable to read LOD model for "+mdlName, Engine::errorType::TYPE_ERROR);
									LoadModel(defaultProps.ReadLines(), "models/default");
								}

							}
						}
					}
			++it;
		}
	}

	// now check each property to make sure it's valid value.
	FileBuffer model(properties.model);
	
	if (!model.Exists()){
		LoadModel(defaultProps.ReadLines(), "models/cube");
	}

	model_properties = properties;

	// load model data from file
	Assimp::Importer importer;
	const aiScene* scene = importer.ReadFile(model.GetDir(), aiProcess_Triangulate | aiProcess_OptimizeMeshes | aiProcess_FlipUVs);

	if (!scene || scene->mFlags == AI_SCENE_FLAGS_INCOMPLETE || !scene->mRootNode)
	{
		Engine::ThrowError(importer.GetErrorString());
		return;
	}
	modelDir = model_properties.model.substr(0, model_properties.model.find_last_of("/"));

	ProcessNode(scene->mRootNode, scene);
	
}

void SceneObject::Draw(){
	for (GLuint i = 0; i < meshes.size(); i++){
		material.UseMaterial();
		
		// send uniforms to shader
		glUniformMatrix4fv(modelUniLoc, 1, GL_FALSE, glm::value_ptr(modelMatrix));
		glUniformMatrix4fv(viewUniLoc, 1, GL_FALSE, glm::value_ptr(gfx::viewMatrix));
		glUniformMatrix4fv(projUniLoc, 1, GL_FALSE, glm::value_ptr(gfx::projectionMatrix));

		glUniform1f(tilingUniLoc, material.GetTiling());
		glUniform4f(colourUniLoc, material.GetColour().r, material.GetColour().g, material.GetColour().b, material.GetColour().a);

		glBindVertexArray(*meshes[i].GetVAO());
		glDrawElements(GL_TRIANGLES, meshes[i].indices.size(), GL_UNSIGNED_INT, 0);

		material.UnBind();
		glBindVertexArray(0);
	}
}

void SceneObject::Think(){

}


void SceneObject::LoadLODs(){

}

int SceneObject::GetPrimCount(){
	return meshes.size();
}
void SceneObject::ProcessNode(aiNode* node, const aiScene* scene){
	
	// process all the node's meshes
	for (GLuint i = 0; i < node->mNumMeshes; i++)
	{
		aiMesh* mesh = scene->mMeshes[node->mMeshes[i]];
		meshes.push_back(ProcessMesh(mesh, scene));
	}

	// process each child

	for (GLuint i = 0; i < node->mNumChildren; i++)
	{
		ProcessNode(node->mChildren[i], scene);
	}
}

Mesh SceneObject::ProcessMesh(aiMesh* mesh, const aiScene* scene)
{
	vector<Vertex> vertices;
	vector<GLuint> indices;
	vector<Texture> textures;

	for (GLuint i = 0; i < mesh->mNumVertices; i++)
	{
		Vertex vertex;
		// Process vertex positions
		glm::vec3 vector;
		vector.x = mesh->mVertices[i].x;
		vector.y = mesh->mVertices[i].y;
		vector.z = mesh->mVertices[i].z;
		vertex.Position = vector;
			
		// Process mesh normals
		vector.x = mesh->mNormals[i].x;
		vector.y = mesh->mNormals[i].y;
		vector.z = mesh->mNormals[i].z;
		vertex.Normals = vector;

		// Process texture coordinates
		if (mesh->mTextureCoords[0]){
			glm::vec2 vec;
			vec.x = mesh->mTextureCoords[0][i].x;
			vec.y = mesh->mTextureCoords[0][i].y;
			vertex.TexCoords = vec;
		}else{
			vertex.TexCoords = glm::vec2(0.0f, 0.0f);
		}

		vertices.push_back(vertex);
	}
	
	// Process indices
	for (GLuint i = 0; i < mesh->mNumFaces; i++)
	{
		aiFace face = mesh->mFaces[i];
		for (GLuint it = 0; it < face.mNumIndices; it++){
			indices.push_back(face.mIndices[it]);
		}
		
	}

	return Mesh(vertices, indices);
}

int SceneObject::GetVerts(){
	return meshes[0].verts.size();
}

Material* SceneObject::GetMaterial(){
	return &material;
}

ModelProperties SceneObject::GetProperties(){
	return model_properties;
}